/*
 * Copyright (c) 2012 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.libermundi.theorcs.core.tapestry.services;

import java.io.IOException;
import java.util.List;
import java.util.Map;

import org.apache.tapestry5.Link;
import org.apache.tapestry5.SymbolConstants;
import org.apache.tapestry5.internal.services.RequestPageCache;
import org.apache.tapestry5.internal.structure.Page;
import org.apache.tapestry5.ioc.Configuration;
import org.apache.tapestry5.ioc.MappedConfiguration;
import org.apache.tapestry5.ioc.OrderedConfiguration;
import org.apache.tapestry5.ioc.Resource;
import org.apache.tapestry5.ioc.ServiceBinder;
import org.apache.tapestry5.ioc.annotations.Decorate;
import org.apache.tapestry5.ioc.annotations.Order;
import org.apache.tapestry5.ioc.annotations.SubModule;
import org.apache.tapestry5.ioc.annotations.Value;
import org.apache.tapestry5.services.ApplicationGlobals;
import org.apache.tapestry5.services.AssetFactory;
import org.apache.tapestry5.services.ComponentClassResolver;
import org.apache.tapestry5.services.Context;
import org.apache.tapestry5.services.LibraryMapping;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.RequestExceptionHandler;
import org.apache.tapestry5.services.Response;
import org.apache.tapestry5.services.ValueEncoderFactory;
import org.libermundi.theorcs.core.CoreConstants;
import org.libermundi.theorcs.core.exceptions.RedirectException;
import org.libermundi.theorcs.core.model.freemarker.FreemarkerEmail;
import org.libermundi.theorcs.core.tapestry.services.assets.AssetModule;
import org.libermundi.theorcs.core.tapestry.services.assets.OfsTemplateLoader;
import org.libermundi.theorcs.core.tapestry.services.assets.OrcsFileSystem;
import org.libermundi.theorcs.core.tapestry.services.configuration.ApplicationConfig;
import org.libermundi.theorcs.core.tapestry.services.configuration.PropertiesApplicationConfiguration;
import org.libermundi.theorcs.core.tapestry.services.csrf.CsrfProtectionModule;
import org.libermundi.theorcs.core.tapestry.services.impl.AppHelperImpl;
import org.libermundi.theorcs.core.tapestry.services.impl.FreeMarkerServiceImpl;
import org.libermundi.theorcs.core.tapestry.services.impl.FreemarkerEmailServiceImpl;
import org.libermundi.theorcs.core.tapestry.services.impl.GenericMultipleValueEncoderFactory;
import org.libermundi.theorcs.core.tapestry.services.impl.GenericValueEncoderFactory;
import org.libermundi.theorcs.core.util.OrcsHome;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import freemarker.cache.TemplateLoader;
import freemarker.template.ObjectWrapper;

/**
 * This module is automatically included as part of the Tapestry IoC Registry, it's a good place to
 * configure and extend Tapestry, or to place your own service definitions.
 */
@SubModule({AssetModule.class,CsrfProtectionModule.class})
public class CoreModule {
    
    public static void contributeApplicationDefaults(MappedConfiguration<String, String> configuration, ApplicationConfig appConfig) {
        // Contributions to ApplicationDefaults will override any contributions to
        // FactoryDefaults (with the same key). Here we're restricting the supported
        // locales to just "en" (English). As you add localised message catalogs and other assets,
        // you can extend this list of locales (it's a comma separated series of locale names;
        // the first locale name is the default when there's no reasonable match).
        configuration.add(SymbolConstants.SUPPORTED_LOCALES, "fr,en");
        
        // The application version number is incorporated into URLs for some
        // assets. Web browsers will cache assets because of the far future expires
        // header. If existing assets are changed, the version number should also
        // change, to force the browser to download new versions.
        configuration.add(SymbolConstants.APPLICATION_VERSION, "4.0.0-SNAPSHOT");
        
        // The factory default is true but during the early stages of an application
        // overriding to false is a good idea. In addition, this is often overridden
        // on the command line as -Dtapestry.production-mode=false
    	configuration.add(SymbolConstants.PRODUCTION_MODE, appConfig.getString(CoreConstants.PRODUCTION_MODE));
    }
	
	public static void contributeComponentClassResolver(final Configuration< LibraryMapping > configuration) {
        configuration.add(new LibraryMapping(CoreConstants.TAPESTRY_MAPPING, "org.libermundi.theorcs.core.tapestry"));
	}
	
	public static void bind(ServiceBinder binder) {
        binder.bind(AppHelper.class, AppHelperImpl.class);
        binder.bind(ValueEncoderFactory.class,GenericValueEncoderFactory.class).withId("GenericValueEncoderFactory");
        binder.bind(MultipleValueEncoderFactory.class,GenericMultipleValueEncoderFactory.class).withId("GenericMultipleValueEncoderFactory");
	}
    
	public static void contributeFactoryDefaults(MappedConfiguration<String, String> configuration) {
        configuration.add("core.assets", "classpath:org/libermundi/theorcs/core/tapestry/assets");

        configuration.add("core.scripts", "${core.assets}/js");
        configuration.add("core.styles", "${core.assets}/css");
        configuration.add("core.images", "${core.assets}/images");
    }
	
	/*
	 * Application Configuration 
	 */
	public static ApplicationConfig buildApplicationConfiguration(final List<String> propertiesPaths) {
		return new PropertiesApplicationConfiguration(propertiesPaths);
	}
	
	public static void contributeApplicationConfiguration(
			OrderedConfiguration<String> configuration,
			OrcsHome theOrcsHome) {
		configuration.add("core-default", "classpath://META-INF/conf/theorcs-default.properties","before:*");
		configuration.add("core-local", theOrcsHome.getPath() + "/conf/theorcs.properties","after:core-default");
	}

	public static void contributeComponentMessagesSource(
			@Value("/META-INF/lang/core.properties")
			Resource mainCatalogResource,
			OrderedConfiguration<Resource> configuration) {
			configuration.add("coreCatalog", mainCatalogResource, "before:AppCatalog");
	}
	
	/*
	 * Email Services
	 */
	public static FreemarkerEmailService buildFreemarkerEmailService(final JavaMailSender mailSender, final Map<String, FreemarkerEmail> configuration, final ThreadPoolTaskExecutor executorService) {
		return new FreemarkerEmailServiceImpl(mailSender,configuration,executorService);
	}
		

	/*
	 * FreeMarker Services
	 */
	
	public static FreeMarkerService buildFreeMarkerService(Context context, Map<String, freemarker.template.Configuration> configuration) {
	        return new FreeMarkerServiceImpl(configuration.get(FreeMarkerService.CONFIG_RESOURCE_KEY));
	}
	
	public static void contributeFreeMarkerService(
            MappedConfiguration<String, freemarker.template.Configuration> configuration,
            ApplicationGlobals globals,
            @OrcsFileSystem
            AssetFactory ofsAssetFactory,
            Request request) {
		freemarker.template.Configuration fmConfig = new freemarker.template.Configuration();
		fmConfig.setWhitespaceStripping(false);
		
		TemplateLoader ofsTemplateLoader = new OfsTemplateLoader(request, ofsAssetFactory);

		fmConfig.setTemplateLoader(ofsTemplateLoader);
		fmConfig.setObjectWrapper(ObjectWrapper.BEANS_WRAPPER);  
		fmConfig.setLocalizedLookup(false);
		
		configuration.add(FreeMarkerService.CONFIG_RESOURCE_KEY, fmConfig);
	}
	
	/*
	 * Handle RedirectException
	 */
	@Decorate(serviceInterface=RequestExceptionHandler.class,id="redirectExceptionHandler")
	@Order("before:*")
    public static RequestExceptionHandler decorateRequestExceptionHandler(
    				final RequestExceptionHandler delegate,
    				final Response response,
    				final AppHelper appHelper,
    				final RequestPageCache requestPageCache,
    				final ComponentClassResolver resolver) {
		
        return new RequestExceptionHandler() {
        	private final Logger logger = LoggerFactory.getLogger(RequestExceptionHandler.class);
            @Override
			public void handleRequestException(Throwable exception) throws IOException {
            	if(logger.isDebugEnabled()) {
            		logger.debug("---------- RedirectException RequestExceptionHandler Decorator ----------");
            	}

            	// check if wrapped
                Throwable cause = exception;
                if (exception.getCause() instanceof RedirectException) {
                    cause = exception.getCause();
                }

                //Better way to check if the cause is RedirectException. Sometimes it's wrapped pretty deep..
                int i = 0;
                while(true){
                    if(cause == null || cause instanceof RedirectException || i > 1000){
                        break;
                    }
                    i++;
                    cause = cause.getCause();
                }

                // check for redirect
                if (cause instanceof RedirectException) {
                    // check for class and string
                    RedirectException redirect = (RedirectException)cause;
                    Link pageLink = redirect.getPageLink();
                    if (pageLink == null) {
                        // handle Class (see ClassResultProcessor)
                        String pageName = redirect.getMessage();
                        Class<?> pageClass = redirect.getPageClass();
                        if (pageClass != null) {
                            pageName = resolver.resolvePageClassNameToPageName(pageClass.getName());
                        }

                        // handle String (see StringResultProcessor)
                        Page page = requestPageCache.get(pageName);
                        pageLink = appHelper.getPageLink(page.getName());
                    }

                    // handle Link redirect
                    if (pageLink != null) {
                    	String redirectURI = pageLink.toRedirectURI();
                    	logger.info("Catched a RedirectException. Redirecting to : " + redirectURI);
                        response.sendRedirect(redirectURI);
                        return;
                    }
                }

                // no redirect so pass on the exception
                delegate.handleRequestException(exception);
            }
        };
    }
}
